Skip to content

fix: AudioSource.waitForPlayout resolving early with audio still queued#693

Merged
chenghao-mou merged 2 commits into
mainfrom
fix/audio-source-latched-playout-promise
Jul 2, 2026
Merged

fix: AudioSource.waitForPlayout resolving early with audio still queued#693
chenghao-mou merged 2 commits into
mainfrom
fix/audio-source-latched-playout-promise

Conversation

@chenghao-mou

@chenghao-mou chenghao-mou commented Jul 1, 2026

Copy link
Copy Markdown
Member

Bug: AudioSource's playout promise is resolved by a drain timer re-armed on each captureFrame, but the promise is only replaced inside waitForPlayout(). Two paths leave it latched-resolved while audio is still streaming: the timer firing during a capture gap longer than the current queue estimate (common at TTS segment start), and clearQueue() (called by agents' output pause(), so every pause pre-latches the next turn). A later waitForPlayout() then resolves immediately with up to queueSizeMs (default 1000ms) still buffered.

Impact: in agents-js this fires playbackFinished ~1s early on every affected turn: the recorder truncates ~1s of agent speech from session recordings, agent_speaking spans under-report, and a pause/handoff right after the turn drops the still-queued tail at the speaker (root cause of "agent speech cut off at the tail without interruption").

Fix: mirror python-sdks, which hit and fixed this same bug in livekit/python-sdks#270 ("fix wait_for_playout finishing too early"): the waiter is discarded when released (drain timer, clearQueue(), close()) and lazily re-created by the next captureFrame, so a stale resolution can never be consumed. Releasing also cancels the drain timer so an orphaned timer can't release a later segment's waiter. One behavior change: waitForPlayout() now resolves immediately when no audio is queued instead of blocking until the next release, matching Python.

Both regression tests fail on the unfixed code and pass with the fix; verified end-to-end against agents-js's recorder pipeline (previously ~1s clip per turn, now 0ms).

🤖 Generated with Claude Code

The internal playout promise could be left resolved (latched) by the
drain timer firing during a gap between captures, or by clearQueue().
A later waitForPlayout() then consumed the stale resolution and
reported playout complete with up to queueSizeMs of audio still
buffered, clipping the tail of agent speech on every turn downstream.

Re-arm the promise on the next captureFrame when it was already
released, and skip the post-playout bookkeeping reset when the promise
was re-armed while waiting.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@changeset-bot

changeset-bot Bot commented Jul 1, 2026

Copy link
Copy Markdown

🦋 Changeset detected

Latest commit: 427bb88

The changes in this PR will be included in the next version bump.

This PR includes changesets to release 1 package
Name Type
@livekit/rtc-node Patch

Not sure what this means? Click here to learn what changesets are.

Click here if you're a maintainer who wants to add another changeset to this PR

@chenghao-mou chenghao-mou requested a review from a team July 1, 2026 20:39

@devin-ai-integration devin-ai-integration Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

✅ Devin Review: No Issues Found

Devin Review analyzed this PR and found no bugs or issues to report.

Open in Devin Review

Discard the waiter when released (timer, clearQueue, close) and lazily
re-create it on the next captureFrame, matching python-sdks' fix for the
same bug (livekit/python-sdks#270). Releasing also cancels the drain
timer so an orphaned timer can't release a later segment's waiter.
waitForPlayout() now resolves immediately when no audio is queued,
matching Python semantics.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@chenghao-mou chenghao-mou merged commit ee6036c into main Jul 2, 2026
12 checks passed
@chenghao-mou chenghao-mou deleted the fix/audio-source-latched-playout-promise branch July 2, 2026 09:17
@github-actions github-actions Bot mentioned this pull request Jul 2, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants